package java

import (
	"fmt"
	. "org.xx/kris2/common"
	"os"
	"path/filepath"
	"strings"
)

var (
	DEFAULT_CONSTANT_PREFIX = "DEFAULT_"
)

func WriteEntitiesCode(rootPackage, destination string, dbEntities []*DbEntity) {
	c := 0
	for _, entity := range dbEntities {
		_, err := WriteEntityCode(rootPackage, destination, entity)
		if err != nil {
			PrintError("%s", err)
		} else {
			c++
		}
	}
	PrintInfo("%d entity(ies) generated", c)
}

func WriteEntityCode(rootPackage, destination string, entity *DbEntity) (string, error) {
	if entity == nil {
		panic("Argument \"entity\" must not be nil")
	}

	resetIndent()

	className := IdentToPascal(entity.Name)

	package_ := getFullName(rootPackage, entity.Package, "domain")
	dir := makePackageDir(destination, package_)
	srcName := filepath.Join(dir, className+".java")

	PrintVerbose("Generating entity: %s (%s) ...", className, srcName)

	f, err := os.Create(srcName)
	if err != nil {
		return "", fmt.Errorf("Can not open file %q for write\n  %s", srcName, err)
	}

	defer f.Close()

	writeFileBriefDoc(f)

	writeCodeLine(f, "package %s;", package_)
	writeBlankLine(f)
	writeCodeLine(f, "import java.util.Objects;")
	writeBlankLine(f)
	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Entity: %s", entity.Name)
	writeCodeLine(f, " * <p>%s</p>", formatDescritpionComment(entity.Name, entity.Description))
	writeCodeLine(f, " * <p>%s</p>", formatLineInfoComment(&entity.LineInfo))
	writeCodeLine(f, " */")
	writeCodeLine(f, "public class %s {", className)
	indent()

	var index int
	var field *DbField

	keys := make([]*DbField, 0, len(entity.Fields))
	for index, field = range entity.Fields {
		if field.Key {
			keys = append(keys, field)
		}
	}
	if len(keys) == 0 {
		// 无法为未指定key的实体生成类。
		return "", fmt.Errorf("Cannot generate class for entity without key: %s", entity.Name)
	}

	// 输出所有的字段默认值常量。
	for index, field = range entity.Fields {
		if field == nil {
			return "", fmt.Errorf("Entity(%s).Fields[%d] must not be null", entity.Name, index)
		}

		if ls, err := formatFieldDefaultValueLiteral(field.DefaultValue, field.Type, field.EnumType, &field.LineInfo); err != nil {
			return "", err
		} else {
			writeCodeLine(f, "private static final %s %s%s = %s;", formatFieldType(field.Type, field.EnumType, &field.LineInfo), DEFAULT_CONSTANT_PREFIX, IdentToUpper(field.Name), ls)
		}
		writeBlankLine(f)
	}

	// 输出所有的字段声明。
	for index, field = range entity.Fields {
		writeCodeLine(f, "private final %s %s;", formatFieldType(field.Type, field.EnumType, &field.LineInfo), IdentToCamel(field.Name))
		writeBlankLine(f)
	}

	// 输出字段名数组。
	allFieldNames := make([]string, len(entity.Fields))
	for index, field = range entity.Fields {
		allFieldNames[index] = formatStringLiteral(IdentToUpper(field.Name))
	}
	writeCodeLine(f, "public static final String[] ALL_FIELD_NAMES = new String[] {%s};", strings.Join(allFieldNames, ", "))
	writeBlankLine(f)

	// 输出构造器。
	if len(keys) < len(entity.Fields) {
		// 如果存在非关键字段，那么生成额外的空构造器。
		writeEntityBlankConstructor(f, entity, className)
	}
	writeEntityConstructor(f, entity, className)

	// 输出get方法。
	for _, field = range entity.Fields {
		writeEntityFieldGetter(f, entity, field)
	}

	// 输出toString方法。
	writeEntityToString(f, entity, className, keys)

	// 输出hashCode方法。
	writeEntityHashCode(f, entity, className, keys)

	// 输出equals方法。
	writeEntityEquals(f, entity, className, keys)

	unIndent()
	writeCodeLine(f, "}")

	return srcName, nil
}

//  writeEntityConstructor 用于输出实体的空白构造器，只能指定主键，其它字段都是默认值。
func writeEntityBlankConstructor(f *os.File, entity *DbEntity, className string) {
	var field *DbField

	// 输出注释。
	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Blank consturctor of %s", className)
	writeCodeLine(f, " *")
	pn := 0
	for _, field = range entity.Fields {
		if field.Key {
			writeCodeLine(f, " * @param %s", IdentToCamel(field.Name))
			writeCodeLine(f, " *        %s", formatDescritpionComment(field.Name, field.Description))
			pn++
		}
	}
	writeCodeLine(f, " */")

	// 输出方法头。
	writeCodeLine(f, "public %s(", className)
	indent()
	indent()

	// 输出参数声明。
	// 输出参数声明。
	pi := 0
	for _, field = range entity.Fields {
		if field.Key {
			var le string
			if pi < pn-1 {
				le = ","
			} else {
				le = ""
			}
			writeCodeLine(f, "%s %s%s", formatFieldType(field.Type, field.EnumType, &field.LineInfo), IdentToCamel(field.Name), le)
			pi++
		}
	}

	unIndent()
	unIndent()
	writeCodeLine(f, ") {")
	indent()

	// 输出参数向字段赋值。
	for _, field = range entity.Fields {
		varName := IdentToCamel(field.Name)
		if field.Key {
			writeCodeLine(f, "this.%s = %s;", varName, varName)
		} else {
			writeCodeLine(f, "this.%s = %s%s;", varName, DEFAULT_CONSTANT_PREFIX, IdentToUpper(field.Name))
		}
	}

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

// writeEntityConstructor 用于输出实体的构造器，允许在构造器中指定实体的每个字段。
func writeEntityConstructor(f *os.File, entity *DbEntity, className string) {
	var field *DbField

	// 输出注释。
	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Consturctor of %s", className)
	writeCodeLine(f, " *")
	for _, field = range entity.Fields {
		writeCodeLine(f, " * @param %s", IdentToCamel(field.Name))
		writeCodeLine(f, " *        %s", formatDescritpionComment(field.Name, field.Description))
	}
	writeCodeLine(f, " */")

	// 输出方法头。
	writeCodeLine(f, "public %s(", className)
	indent()
	indent()

	// 输出参数声明。
	pn := len(entity.Fields)
	pi := 0
	for _, field = range entity.Fields {
		var le string
		if pi < pn-1 {
			le = ","
		} else {
			le = ""
		}
		writeCodeLine(f, "%s %s%s", formatFieldType(field.Type, field.EnumType, &field.LineInfo), IdentToCamel(field.Name), le)
		pi++
	}

	unIndent()
	unIndent()
	writeCodeLine(f, ") {")
	indent()

	// 输出参数向字段赋值。
	for _, field = range entity.Fields {
		varName := IdentToCamel(field.Name)
		writeCodeLine(f, "this.%s = %s != null ? %s : %s%s;", varName, varName, varName, DEFAULT_CONSTANT_PREFIX, IdentToUpper(field.Name))
	}

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

func writeEntityFieldGetter(f *os.File, entity *DbEntity, field *DbField) {
	upperName := IdentToUpper(field.Name)

	writeCodeLine(f, "/**")
	writeCodeLine(f, " * Get %s", upperName)
	writeCodeLine(f, " * <p>%s</p>", formatDescritpionComment(field.Name, field.Description))
	writeCodeLine(f, " * <p>%s</p>", formatLineInfoComment(&entity.LineInfo))
	writeCodeLine(f, " * @return value of %s", upperName)
	writeCodeLine(f, " */")

	var prefix string
	if field.Type == FtBoolean {
		prefix = "is"
	} else {
		prefix = "get"
	}

	writeCodeLine(f, "public %s %s%s() {", formatFieldType(field.Type, field.EnumType, &field.LineInfo), prefix, IdentToPascal(field.Name))
	indent()

	writeCodeLine(f, "return this.%s;", IdentToCamel(field.Name))

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

func writeEntityToString(f *os.File, entity *DbEntity, className string, keys []*DbField) {
	writeCodeLine(f, "@Override")
	writeCodeLine(f, "public String toString() {")
	indent()

	if len(keys) != 0 {
		buf := []string{}
		for _, key := range keys {
			fieldName := IdentToCamel(key.Name)
			buf = append(buf, fmt.Sprintf("%s + \"=\" + %s", formatStringLiteral(fieldName), fieldName))
		}
		writeCodeLine(f, "return \"%s{\" + %s + \"}\";", className, strings.Join(buf, " + \", \" + "))
	} else {
		writeCodeLine(f, "return super.toString();")
	}

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

func writeEntityHashCode(f *os.File, entity *DbEntity, className string, keys []*DbField) {
	writeCodeLine(f, "@Override")
	writeCodeLine(f, "public int hashCode() {")
	indent()

	if len(keys) != 0 {
		buf := []string{}
		for _, key := range keys {
			fieldName := IdentToCamel(key.Name)
			buf = append(buf, "this."+fieldName)
		}
		writeCodeLine(f, "return Objects.hash(%s);", strings.Join(buf, ", "))
	} else {
		writeCodeLine(f, "return super.hashCode();")
	}

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
	writeBlankLine(f)
	writeBlankLine(f)
}

func writeEntityEquals(f *os.File, entity *DbEntity, className string, keys []*DbField) {
	writeCodeLine(f, "@Override")
	writeCodeLine(f, "public boolean equals(")
	indent()
	indent()
	writeCodeLine(f, "Object other")
	unIndent()
	unIndent()
	writeCodeLine(f, ") {")
	indent()

	writeCodeLine(f, "if (other == this) return true;")
	if len(keys) != 0 {
		writeCodeLine(f, "if (!(other instanceof %s )) return false;", className)
		writeCodeLine(f, "final %s o = (%s) other;", className, className)
		buf := []string{}
		for _, key := range keys {
			fieldName := IdentToCamel(key.Name)
			buf = append(buf, fmt.Sprintf("Objects.equals(this.%s, o.%s)", fieldName, fieldName))
		}
		writeCodeLine(f, "return %s;", strings.Join(buf, " && "))
	} else {
		writeCodeLine(f, "return false;")
	}

	// 输出方法结束。
	unIndent()
	writeCodeLine(f, "}")
}
